package com.lvhx.springboot.base;

import lombok.extern.slf4j.Slf4j;

import java.util.LinkedHashMap;
import java.util.Map;

@Slf4j
public class DefaultUnitOfWork implements UnitOfWork {

    // 是否启动
    private boolean isStarted;
    private final TransactionManager transactionManager;
    private Object backingTransaction;
    private final Map<AggregateRoot, AggregateEntry> registeredAggregates = new LinkedHashMap<>();
    private boolean isCommitted = false;


    public DefaultUnitOfWork(TransactionManager<?> transactionManager) {
        this.transactionManager = transactionManager;
    }

    public DefaultUnitOfWork() {
        this(null);
    }

    @Override
    public void commit() {
        assertStarted();

        try {
            saveAggregates();
            isCommitted = true;
            doCommit();
            stop();
        } catch (RuntimeException e) {
            doRollback(e);
            stop();
            throw e;
        } finally {
            clear();
        }
    }

    protected void clear() {
        CurrentUnitOfWork.clear(this);
    }

    protected void doRollback(Throwable cause) {
        registeredAggregates.clear();
        try {
            if (backingTransaction != null) {
                transactionManager.rollbackTransaction(backingTransaction);
            }
        } finally {

        }
    }

    private void stop() {
        isStarted = false;
    }

    protected void doCommit() {
        if (isTransactional()) {
//            transactionManager.commitTransaction(transactionManager);
            transactionManager.commitTransaction(backingTransaction);
        }
    }

    private void assertStarted() {
        if (!isStarted) {
            throw new IllegalStateException("UnitOfWork is not started");
        }
    }

    private void saveAggregates() {
        for (AggregateEntry entry : registeredAggregates.values()) {
            entry.saveAggregate();
        }
        registeredAggregates.clear();
    }

    @Override
    public void start() {

        if (isStarted) {

        }
        doStart();

        CurrentUnitOfWork.set(this);
        isStarted = true;
    }

    @Override
    public boolean isTransactional() {
        return transactionManager != null;
    }

    @Override
    public void rollback(Throwable cause) {
        if (cause != null) {

        }

        try {
            if (isStarted) {
                doRollback(cause);
            }
        } finally {
            clear();
            stop();
        }
    }

    @Override
    public <T extends AggregateRoot> T getAggregate(Class<T> aggregateType, Object identifier) {
        T similarAggregate = (T) findSimilarAggregate(aggregateType, identifier);
        return similarAggregate;
    }

    /*
     * 注册工作单元使用的聚合根对象
     */
    @SuppressWarnings({"unchecked"})
    @Override
    public <T extends AggregateRoot> T registerAggregate(T aggregate, SaveAggregateCallback<T> saveAggregateCallback) {
        T similarAggregate = (T) findSimilarAggregate(aggregate.getClass(), aggregate.getIdentifier());
        if (similarAggregate != null) {
            if (log.isInfoEnabled()) {
                log.info("Ignoring aggregate registration. An aggregate of same type and identifier was already registered in this Unit Of Work: type [{}], identifier [{}]",
                        aggregate.getClass().getSimpleName(),
                        aggregate.getIdentifier());
            }
            return similarAggregate;
        }
        registeredAggregates.put(aggregate, new AggregateEntry<T>(aggregate, saveAggregateCallback));
        return aggregate;
    }

    /**
     * 根据传入聚合根特征查找聚合根对象
     *
     * @param aggregateType
     * @param identifier
     * @return
     */
    @SuppressWarnings({"unchecked"})
    private <T extends AggregateRoot> T findSimilarAggregate(Class<T> aggregateType, Object identifier) {

        for (AggregateRoot aggregate : registeredAggregates.keySet()) {
            if (aggregateType.isInstance(aggregate)
                    && identifier.equals(aggregate.getIdentifier())) {
                return (T) aggregate;
            }
        }
        return null;
    }


    public static UnitOfWork startAndGet(TransactionManager<?> transactionManager) {
        DefaultUnitOfWork uow = new DefaultUnitOfWork(transactionManager);
        uow.start();
        return uow;
    }

    protected void doStart() {
        if (isTransactional()) {
            this.backingTransaction = transactionManager.startTransaction();
        }
    }

    private static class AggregateEntry<T extends AggregateRoot> {
        private final T aggregateRoot;
        private final SaveAggregateCallback<T> callback;

        public AggregateEntry(T aggregateRoot, SaveAggregateCallback<T> callback) {
            this.aggregateRoot = aggregateRoot;
            this.callback = callback;
        }

        public void saveAggregate() {
            callback.save(aggregateRoot);
        }
    }
}
